In [1]:
%%html
<style>
div.input {
    display:none;
}
</style>

Canada COVID-19 Trend

Data Sources:

In [2]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
%matplotlib inline
import seaborn as sns
import plotly.express as px
from plotly.subplots import make_subplots
sns.set(context="notebook", style="whitegrid", palette="deep", font="Arial", font_scale=1, color_codes=True)
In [3]:
from sklearn.linear_model import LinearRegression
from datetime import timedelta
import plotly.graph_objects as go
import scipy.optimize as opt


from statsmodels.api import OLS
from IPython.display import display, HTML
from scipy.optimize import curve_fit
In [4]:
#Read in data. Canada data is from the Canada gov website itself
data_src="https://github.com/CSSEGISandData/COVID-19/raw/master/csse_covid_19_data/csse_covid_19_time_series/"
cfm=pd.read_csv(data_src+"time_series_covid19_confirmed_global.csv")
dt=pd.read_csv(data_src+"time_series_covid19_deaths_global.csv")

can=pd.read_csv("https://health-infobase.canada.ca/src/data/covidLive/covid19.csv")
can=can[can['prname']!='Canada'][['date','prname','numtotal','numdeaths']]
can.date=pd.to_datetime(can.date,format='%d-%m-%Y').dt.strftime('%Y-%m-%d')
can['Country']='Canada'
tmp_cfm=can.pivot_table(values='numtotal',index=['Country','prname'],columns='date')
tmp_dt=can.pivot_table(values='numdeaths',index=['Country','prname'],columns='date')
In [5]:
def df_reshape(df,df_can,var, reglist):
    df.rename(columns={'Country/Region':'Country', 'Province/State':'Region'},inplace=True)
    df.Region.fillna('',inplace=True)
    df=df.drop(columns=['Lat', 'Long']).groupby(['Country','Region']).sum()
    df.columns=pd.to_datetime(pd.Series(df.columns),format='%m/%d/%y').dt.strftime('%Y-%m-%d')
    maxdate=min(max(df.columns),max(df_can.columns))
    df=df[df.columns[df.columns<=maxdate]]
    df_can=df_can[df_can.columns[df_can.columns<=maxdate]]
    tp=pd.MultiIndex.from_tuples(tuple([['Canada',x] for x in reglist]))
    new_can=pd.DataFrame(index=tp,columns=df.columns)
    for pr in reglist:
        new_can.loc[('Canada',pr),df_can.columns]=df_can.loc[('Canada',pr)]
    new_can.fillna(0,inplace=True)
    new_can=new_can.cummax(axis=1)
    
    df.drop(index='Canada',inplace=True)
    df=df.append(new_can)

    idxname=df.index.names
    df_new=pd.melt(df.reset_index(),id_vars=idxname,\
                   value_vars=df.columns,var_name='Date_updated',value_name=var)
    df_new[var+'_added']=df_new[var]-df_new.groupby(idxname)[var].shift(1).fillna(0)
    df_new['Date_updated']=pd.to_datetime(df_new['Date_updated'])
    return(df_new, df)
In [6]:
def gauss_two(x, mu1, sig1, a1, mu2, sig2, a2):
    y1=a1*np.exp(-np.power(x - mu1, 2.) / (2 * np.power(sig1, 2.)))
    y2=a2*np.exp(-np.power(x - mu2, 2.) / (2 * np.power(sig2, 2.)))
    return(y1+y2)


def gauss_one(x, mu, sig, a):
    y=a*np.exp(-np.power(x - mu, 2.) / (2 * np.power(sig, 2.)))
    return(y)
In [7]:
df, df_wide=df_reshape(cfm,tmp_cfm, var='Confirmed', reglist=can.prname.unique())
df2, df2_wide=df_reshape(dt,tmp_dt, var='Death', reglist=can.prname.unique())

df=df.merge(df2,on=['Country','Region','Date_updated'],how='left')

maxdate=df['Date_updated'].max()

date_max=(maxdate + np.timedelta64(15,'D')).strftime('%Y-%m-%d')
In [8]:
print(f'Data version: {maxdate.strftime("%Y-%m-%d")}')
Data version: 2020-08-30
In [9]:
df_country=df.groupby(['Country','Date_updated'])[['Confirmed','Death','Confirmed_added','Death_added']].sum().reset_index()
df_now=df_country.groupby('Country')[['Confirmed','Death']].max().reset_index()
In [10]:
dfa=df_country[df_country.Country.isin(df_now[df_now.Confirmed > 300]['Country'])].copy()
dfa.loc[:,'StartDate']=-999
for j in dfa.Country.unique():
    sdate=dfa.loc[(dfa.Country==j) & (dfa.Confirmed > 100),'Date_updated'].min()
    ##print(f"Country: {j} case number: {dfa[(dfa.Country==j) & (dfa.Confirmed > 100)]['Confirmed'].min()} start date {sdate}")
    tmp=dfa.loc[dfa.Country==j,'Date_updated']-sdate
    dfa.loc[dfa.Country==j,'StartDate']=tmp.dt.days + 1
    if j == 'China':
          dfa.loc[dfa.Country==j,'StartDate']+=5

Canada COVID-19 case growth rate compared to other countries

When aligned by the first day since the 100th case in each country, the total confirmed case number of all the countries grow similarly except for a few: it follows a steep exponential growth at the early stage which is resulted from an unhampered epidemic spread, gradually slows down after various countermeasures are taken by the governments and eventually goes to flat when the situation is under control.

Countries that are successful in the early prevention and control, like Japan and Singapore, show a much shallower growth with time; South Korea experienced a rapid growth at the early stage but managed to flatten out the curve very quick by an aggressive 'trace, test and treat' strategy.

Canada currently is still in the early rising stage with a growth rate similar to many European countries. The countermeasures taken by the governments are yet to significantly slow down the growth.

Three northern European countries, Sweden, Norway and Denmark, show much shallower growth compared to other countries and this has led to a lot of discussions on the cultural difference of the northern countries that might help to enforce the social distance more effectively.

Two European countries, Czechia and Austria, have been requiring their risidents to wear face masks in public, which is different from other country's policy. It is interesting to monitoring their case growth to see the impact of this difference.

In [11]:
#countrylist=allcountrylist[:10]
#countrylist.append('Japan')


cl=px.colors.qualitative.Dark24
cblue=cl[0]
corange=cl[8]
cgreen=cl[2]
cred=cl[3]

cl2=[x for x in cl if x not in [cblue,corange,cgreen,cred]]


#countrylist.remove('China').remove('Korea, South').remove('Italy')
fig=px.line(log_y=True,range_x=[0,200],range_y=[100,10000000],width=1000, height=600)
#fig=px.line(dfa[dfa.Country.isin(countrylist)],x='StartDate',y='Confirmed',color='Country',log_y=True
#            ,range_x=[0,40],range_y=[100,100000])

cty='China'
clr=cblue #blue
fig.add_scatter(x=dfa.loc[dfa.Country==cty,'StartDate'],y=dfa.loc[dfa.Country==cty,'Confirmed']
                ,mode="lines",name=cty,line=dict(width=2, color=clr))

cty='Italy'
clr=corange #Orange
fig.add_scatter(x=dfa.loc[dfa.Country==cty,'StartDate'],y=dfa.loc[dfa.Country==cty,'Confirmed']
                ,mode="lines",name=cty,line=dict(width=2, color=clr))


cty='US'
clr=cgreen #green
fig.add_scatter(x=dfa.loc[dfa.Country==cty,'StartDate'],y=dfa.loc[dfa.Country==cty,'Confirmed']
                ,mode="lines",name=cty,line=dict(width=2,color=clr))

ctryl=['Spain','Germany','Iran','France','Switzerland','United Kingdom','Japan',
       'Singapore','Czechia','Denmark','Sweden','Austria','Norway','Brazil','Russia']
for j in range(len(ctryl)):
    fig.add_scatter(x=dfa.loc[dfa.Country==ctryl[j],'StartDate'],y=dfa.loc[dfa.Country==ctryl[j],'Confirmed']
                ,mode="lines",name=ctryl[j],line=dict(width=2,color=cl2[j]))

cty='Korea, South'
clr='yellow'
fig.add_scatter(x=dfa.loc[dfa.Country==cty,'StartDate'],y=dfa.loc[dfa.Country==cty,'Confirmed']
                ,mode="lines",name=cty,line=dict(width=2,color=clr))


cty='Canada'
clr=cred #red
fig.add_scatter(x=dfa.loc[dfa.Country==cty,'StartDate'],y=dfa.loc[dfa.Country==cty,'Confirmed']
                ,mode="lines+markers",name=cty,marker=dict(size=2,color=clr,line=dict(width=4,color=clr)))



fig.update_layout(
    title="COVID-19 Cases by Country",title_x=0.5,
    xaxis=dict(showline=True,showgrid=True,showticklabels=True,linecolor='black',linewidth=2,
               ticks='outside', mirror=True,
              gridcolor="rgba(0,0,0,0.2)",
              tickfont=dict(
            family='Arial',
            size=12,
            color='black',
        )),
    yaxis=dict(showline=True,showgrid=True,showticklabels=True,linecolor='black',linewidth=2,
               ticks='inside',mirror=True,
               gridcolor="rgba(0,0,0,0.2)",
              tickfont=dict(
            family='Arial',
            size=12,
            color='black',
        )),
    plot_bgcolor='white',
    xaxis_title="Number of Days since 100th Case",
    yaxis_title="Confirmed Case",
    font=dict(
        family="Arial",
        size=14,
        color="black"
    )
)

fig.show()

#fig.write_image("output/trend_by_country"+ dfa.Date_updated.max().strftime('_%m_%d_%Y')+".jpeg", scale=3)
In [12]:
s1_cnd=dfa.loc[(dfa.Country=='Canada') & (dfa.Confirmed > 1000),'Date_updated'].min()

pred=pd.DataFrame({'Date_updated':[ s1_cnd  + timedelta(days=x-9) for x in range(250)]})

pred=pred.merge(dfa.loc[dfa.Country=='Canada',['Date_updated','Confirmed']], how='left',on='Date_updated')




dcan=dfa.loc[dfa.Country=='Canada',['Date_updated','Confirmed']].tail(5)
x=np.arange(10)
xx=np.array([np.repeat(1,10),x]).transpose()
mod=OLS(np.log(dcan['Confirmed']), xx[:5]).fit()
pdx=mod.get_prediction(xx)
predmean=np.exp(pdx.predicted_mean)
conf=np.exp(pdx.conf_int(alpha=0.05))

out=pd.DataFrame({'Date_updated': dcan['Date_updated'].append(dcan['Date_updated']+timedelta(days=5)), 
                  'predict':predmean, 
                  'cf_low':conf[:,0],'cf_high':conf[:,1]})

pred=pred.merge(out,how='left',on='Date_updated')




pred['Daily Adds']=float('nan')
tmp=pred.Confirmed.iloc[1:(len(pred)-1)].values-pred.Confirmed.iloc[0:(len(pred)-2)].values
pred.iloc[1:(len(pred)-1),5]=tmp.copy()

cty='US'
tmp=dfa.loc[dfa.Country==cty,['Date_updated','Confirmed']]
tmp['Date_updted_'+cty]=tmp['Date_updated']
tmp['Date_updated']=tmp['Date_updated'] - tmp.loc[tmp.Confirmed > 1000, 'Date_updated'].min() + s1_cnd
tmp.rename(columns={"Confirmed": cty + " - Date Adjusted"}, inplace=True)
pred=pred.merge(tmp,how='left', on='Date_updated')

cty='Brazil'
tmp=dfa.loc[dfa.Country==cty,['Date_updated','Confirmed']]
tmp['Date_updted_'+cty]=tmp['Date_updated']
tmp['Date_updated']=tmp['Date_updated'] - tmp.loc[tmp.Confirmed > 1000, 'Date_updated'].min() + s1_cnd
tmp.rename(columns={"Confirmed": cty + " - Date Adjusted"}, inplace=True)

pred=pred.merge(tmp,how='left', on='Date_updated')

cty='Russia'
tmp=dfa.loc[dfa.Country==cty,['Date_updated','Confirmed']]
tmp['Date_updted_'+cty]=tmp['Date_updated']
tmp['Date_updated']=tmp['Date_updated'] - tmp.loc[tmp.Confirmed > 1000, 'Date_updated'].min() + s1_cnd
tmp.rename(columns={"Confirmed": cty + " - Date Adjusted"}, inplace=True)

pred=pred.merge(tmp,how='left', on='Date_updated')

cty='United Kingdom'
tmp=dfa.loc[dfa.Country==cty,['Date_updated','Confirmed']]
tmp['Date_updted_'+cty]=tmp['Date_updated']
tmp['Date_updated']=tmp['Date_updated'] - tmp.loc[tmp.Confirmed > 1000, 'Date_updated'].min() + s1_cnd
tmp.rename(columns={"Confirmed": cty + " - Date Adjusted"}, inplace=True)

pred=pred.merge(tmp,how='left', on='Date_updated')


eve=[['2020-03-13','Recommendation against Intl. travel'],
     ['2020-03-16','State of Emergency'],
     ['2020-03-17','Restricted entry into Canada'],
     ['2020-03-20','US-Canada border closed for non-essential travel'],
     ['2020-03-24','Quarantine Act invoked'],
     ['2020-03-30','Domestic travel restriction']]

eve=pd.DataFrame(eve, columns=['Date_updated','events'])
eve.Date_updated=pd.to_datetime(eve.Date_updated)

pred=pred.merge(eve,how='left',on='Date_updated')

pred['predict_adds']=pred['predict']-pred['predict'].shift(1)



#pred.to_csv('output/CanadaGrowth'+ dfa.Date_updated.max().strftime('_%m_%d_%Y')+'.csv')
                   

Exponential projection of Canada case growth and the impact of the countermeasures

Since Canada is still at the exponential growth period, an exponential projection provides a good short-term forecast. In the following chart, the next 5 day's case number is projected based on the exponential growth of the previous 5 day's actual case numbers. The different countermeasures taken by the government are timelined on the chart to monitor their impacts on the growth.

After reaching 1000 cases, the growth rate of Canada is actually not very different from the growth rate of many other countries when they had a similar number of cases. The plot below shows the growth curve of Canada vs US, Italy, Korean and China after other countries' dates are backshifted. The trajectories of those countries provide different scenaria on the long term case growth in Canada.

As of today, we can see that the Canada growth curve finally started to fall below both US and Italy, suggesting we are slowing down faster than those countries.

In [13]:
fig=px.line(log_y=True,range_x=['2020-03-08',date_max],range_y=[100,10000000],width=900, height=600)

captext="Canada COVID-19 case growth with time. US, Brazil, Russia and UK's curves are shifted by days to align with<br>"\
+ "the 1000th case day in Canada. Exponential projection of the future 5 days are calculated using the previous 5 day<br>"\
+"numbers. The shaded area indicates the 95% confidence interval of the projection."
     


cl=['green','orange','gold','blue']
cty=['US', 'Brazil', 'Russia', 'United Kingdom']
lname=['US - date shifted', 'Brazil - date shifted',
      'Russia - date shifted','United Kingdom - date shifted']

for i in range(len(cl)):
    fig.add_scatter(x=pred['Date_updated'],y=pred[cty[i] +' - Date Adjusted'], mode="lines",
                   name=lname[i],line=dict(width=3,color=cl[i]))
    
    
fig.add_trace(go.Scatter(x=pred['Date_updated'], y=pred['cf_low'], 
                         fill=None, mode='lines',line_color='rgba(0,0,0,0)',showlegend=False))
fig.add_trace(go.Scatter(x=pred['Date_updated'], y=pred['cf_high'],
    fill='tonexty',mode='lines', line_color='rgba(256,0,0,0.3)',showlegend=False))    


fig.add_scatter(x=pred['Date_updated'],y=pred['predict'], mode="lines",
               name='Canada exponential projection',line=dict(width=2, color='red', dash='dot'))
                   

fig.add_scatter(x=pred['Date_updated'],y=pred['Confirmed'], mode="lines+markers+text",
               name='Canada',marker=dict(size=5,color='red'),
               text=pred['events'],textposition="bottom right",
               textfont=dict(family="Ariel",size=14,color='red'))




fig.update_layout(title='Canada COVID-19 Case Growth Curve',title_x=0.5, 
                  titlefont=dict(family='Arial',size=28,color='black'),
                  plot_bgcolor='white',
                  xaxis=dict(titlefont=dict(family='Ariel, sans-serif',size=11, color='black'),
                             showline=True,showgrid=True,showticklabels=True,linecolor='black',
                             linewidth=2,ticks='outside', mirror=False,
                             gridcolor="rgba(0,0,0,0.1)",
                             tickfont=dict(family='Arial',size=15,color='black'),
                             tickformat= "%d-%b"
                            ),
                  yaxis=dict(title='Confirmed Case', titlefont=dict(family='Ariel',size=18, color='black'),
                             showline=True,showgrid=True,showticklabels=True,linecolor='black',
                             linewidth=2,ticks='outside', mirror=False,
                             gridcolor="rgba(0,0,0,0.1)",
                             tickfont=dict(family='Arial',size=15,color='black')
                            ),
    legend=dict(x=0.63, y=0.05,
        font=dict(family="Ariel",size=16,color="black"), bgcolor='rgba(0,0,0,0)'),
    margin = dict(l = 50, r = 50, t = 60, b = 120),              
    annotations = [dict(x = -0.08, y = -0.25, align='left',
    text = captext,  showarrow = False, xref='paper', yref='paper', 
      xanchor='left', yanchor='auto', xshift=0, yshift=0,
      font=dict(family='Arial',size=16, color="grey"))]
                 )

fig.show()

#fig.write_image("output/Canada_growth_"+ dfa.Date_updated.max().strftime('_%m_%d_%Y')+".jpeg", scale=3)

Projected case number in the next 5 days:

In [14]:
outtable=pred.loc[~pred.predict.isna(),['Date_updated','Confirmed','predict','cf_low','cf_high','Daily Adds', 'predict_adds']].tail(8).copy()
outtable['Date']=outtable['Date_updated'].dt.strftime('%B %d')

outtable['Actual']=[ f"{x:,.0f}" if ~np.isnan(x) else '' for x in outtable['Confirmed'] ]

outtable['Projection']=[f"{x:,.0f}" for x in outtable['predict']]

outtable['Daily Adds']=[f"{x:,.0f}" if ~np.isnan(x) else '' for x in outtable['Daily Adds'] ]

outtable['Projected Adds']=[f"{x:,.0f}" for x in outtable['predict_adds']]
outtable=outtable[['Date','Actual','Projection','Daily Adds','Projected Adds']].set_index('Date').T


                                                
display(HTML(outtable.to_html()))                                                  
Date August 28 August 29 August 30 August 31 September 01 September 02 September 03 September 04
Actual 127,310 127,673 127,940
Projection 127,230 127,621 128,013 128,406 128,801 129,197 129,593 129,991
Daily Adds 492 363 267
Projected Adds 390 391 392 393 394 396 397 398

Canada daily case increase

In [15]:
fig = make_subplots(
    rows=5, cols=1, 
    subplot_titles=("Canada","US", "Brazil","Russia","UK"),
    vertical_spacing=0.08,
    specs=[[{"secondary_y": True}],
           [{"secondary_y": True}],
           [{"secondary_y": True}],
           [{"secondary_y": True}],
           [{"secondary_y": True}]])

bw=1000*3600*24

oneday=86400000.0

#date_max='2020-08-30'

#Canada
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='Canada','Date_updated'],
                y=dfa.loc[dfa.Country=='Canada','Confirmed_added'] ,
                name='Case',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5                     
                ),
             row=1, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='Canada','Date_updated'],
                y=dfa.loc[dfa.Country=='Canada','Death_added'] ,
                name='Death',
                marker_color='blue',
                width=bw*0.5     
                ),
             row=1, col=1, secondary_y=True)

fig.add_trace(go.Bar(x=pd.to_datetime(['2020-02-26','2020-03-13','2020-03-17','2020-03-25'])
                     , y=np.repeat(100000,4),name='events', marker_color='rgb(256,0,0)', width=bw*0.2,showlegend=False),
             row=1,col=1, secondary_y=False)

fig.add_annotation(x=pd.to_datetime('2020-02-26'), y=2700, ax=140, ay=0, xref='x1',yref='y1',arrowhead=1,
            align='left',text="Minister of Health issued warning for COVID-19", font=dict( size=11))

fig.add_annotation(x=pd.to_datetime('2020-03-13'), y=2500, ax=100, ay=0, xref='x1',yref='y1',arrowhead=1,
            align='left',text="Recommendation against Intl. travel", font=dict( size=11))

fig.add_annotation(x=pd.to_datetime('2020-03-17'),y=2200, ax=110, ay=0, xref='x1',yref='y1',arrowhead=1,
            align='left',text="Travel bans; State of Emergency by Provs", font=dict( size=11))


fig.add_annotation(x=pd.to_datetime('2020-03-25'), y=2000, ax=60, ay=0,xref='x1',yref='y1',arrowhead=1,
            align='left',text="Quarantine Act", font=dict( size=11))



fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-03-10',date_max]), row=1, col=1,showgrid=True)
yr=2800
fig.update_yaxes(title_text='Case',range=[0,yr], row=1, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=1, col=1, secondary_y=True,color='blue')







#US
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='US','Date_updated'],
                y=dfa.loc[dfa.Country=='US','Confirmed_added'] ,
                name='US',
                marker_color='rgb(55, 83, 109)',
                     width=bw*0.5,showlegend=False
                ),
             row=2, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='US','Date_updated'],
                y=dfa.loc[dfa.Country=='US','Death_added'] ,
                name='US',
                marker_color='blue',
                     width=bw*0.5,showlegend=False
                ),
             row=2, col=1, secondary_y=True)


fig.add_trace(go.Bar(x=pd.to_datetime(['2020-02-29','2020-03-19'])
                     , y=np.repeat(100000,2),name='events', marker_color='rgb(256,0,0)',width=bw*0.2,showlegend=False),
             row=2, col=1, secondary_y=False)


fig.add_annotation(x=pd.to_datetime('2020-02-29'),y=55000, ax=80, ay=0, xref='x2', yref='y3',arrowhead=1,
            text="WA: State of Emergency",font=dict( size=11))

fig.add_annotation(x=pd.to_datetime('2020-03-19'), y=60000, ax=100, ay=0, xref='x2', yref='y3',arrowhead=1,
            text="More states lockdown; Travel bans",font=dict( size=11))

fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-02-23',date_max]), row=2, col=1,showgrid=True)
yr=80000
fig.update_yaxes(title_text='Case',range=[0,yr], row=2, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=2, col=1, secondary_y=True,color='blue')


#Brazil

fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='Brazil','Date_updated'],
                y=dfa.loc[dfa.Country=='Brazil','Confirmed_added'] ,
                name='Brazil',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5,showlegend=False   
                ),
             row=3, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='Brazil','Date_updated'],
                y=dfa.loc[dfa.Country=='Brazil','Death_added'] ,
                name='Brazil',
                marker_color='blue',
                width=bw*0.5,showlegend=False   
                ),
             row=3, col=1, secondary_y=True)


#fig.add_trace(go.Bar(x=pd.to_datetime(['2020-03-09'])
#                     , y=np.repeat(100000,1),name='events', marker_color='rgb(256,0,0)',width=bw*0.2,showlegend=False ),
#             row=3, col=1, secondary_y=False)


#fig.add_annotation(x=pd.to_datetime('2020-03-10'),y=7000, ax=100,ay=0,xref='x3', yref='y5',arrowhead=1,
#            text="Lockdown: Mar 09",font=dict(size=11))

#fig.add_annotation(x=pd.to_datetime('2020-03-22'),y=6500,ax=70,ay=0, xref='x3', yref='y5',arrowhead=1,
#            text="Peak: Mar 21",font=dict(size=11))

#fig.add_annotation(x=pd.to_datetime('2020-05-15'),y=6000,ax=0,ay=0, xref='x3', yref='y5',
#            text="From lockdown to peak:<br>12 days",font=dict(color="black",size=14))


fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-02-23',date_max]), row=3, col=1,showgrid=True)
yr=100000
fig.update_yaxes(title_text='Case',range=[0,yr], row=3, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=3, col=1, secondary_y=True,color='blue')





#Russia
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='Russia','Date_updated'],
                y=dfa.loc[dfa.Country=='Russia','Confirmed_added'] ,
                name='Russia',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5 ,showlegend=False  
                ),
             row=4, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='Russia','Date_updated'],
                y=dfa.loc[dfa.Country=='Russia','Death_added'] ,
                name='Russia',
                marker_color='blue',
                width=bw*0.5 ,showlegend=False    
                ),
             row=4, col=1, secondary_y=True)

#fig.add_trace(go.Bar(x=[pd.to_datetime('2020-02-25')]
#                     , y=[100000], name='events', marker_color='rgb(256,0,0)',width=bw*0.2,showlegend=False),
#             row=4, col=1, secondary_y=False)


#fig.add_annotation(
#            x=pd.to_datetime('2020-02-26'),y=1000,ax=120,ay=0,xref='x4',yref='y7',arrowhead=1,
#            text="Soft lockdown <br> aggressive testing and tracing: <br> Feb 25", font=dict(size=11))

#fig.add_annotation(x=pd.to_datetime('2020-03-04'),y=700,ax=70, ay=0, xref='x4',yref='y7',arrowhead=1,
#            text="Peak: Mar 3", font=dict(size=11))

#fig.add_annotation(
#            x=pd.to_datetime('2020-05-03'),y=800,ax=0,ay=0,xref='x4',yref='y7',
#            text="From aggressive measures to peak: <br> 7 days",font=dict( color="black",size=14))
fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-02-23',date_max]), row=4, col=1,showgrid=True)
yr=12000
fig.update_yaxes(title_text='Case',range=[0,yr], row=4, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=4, col=1, secondary_y=True,color='blue')




#United Kingdom
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='United Kingdom','Date_updated'],
                y=dfa.loc[dfa.Country=='United Kingdom','Confirmed_added'] ,
                name='United Kingdom',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5,showlegend=False
                ),
             row=5, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=dfa.loc[dfa.Country=='United Kingdom','Date_updated'],
                y=dfa.loc[dfa.Country=='United Kingdom','Death_added'] ,
                name='United Kingdom',
                marker_color='blue',
                width=bw*0.5,showlegend=False  
                ),
             row=5, col=1, secondary_y=True)

#fig.add_trace(go.Bar(x=pd.to_datetime(['2020-01-23','2020-03-25'])
#                     , y=np.repeat(100000,2),name='events', marker_color='rgb(256,0,0)',width=bw*0.5,showlegend=False),
#             row=5,col=1, secondary_y=False)

#fig.add_annotation(
#            x=pd.to_datetime('2020-01-23'),y=16000,ax=80,ay=0,xref='x5',yref='y9',arrowhead=1,
#            text="Hubei lockdown:<br> Jan 23", font=dict(size=11))

#fig.add_annotation(x=pd.to_datetime('2020-03-25'),y=12000,ax=-70, ay=0, xref='x5',yref='y9',arrowhead=1,
#            text="Lockdown Lifted: <br> Mar 25", font=dict(size=11))

#fig.add_annotation(x=pd.to_datetime('2020-02-13'),y=10000,ax=70, ay=0, xref='x5',yref='y9',arrowhead=1,
#            text="Peak: Feb 11", font=dict(size=11))

#fig.add_annotation(
#            x=pd.to_datetime('2020-05-15'),y=14000,ax=0,ay=0,xref='x5',yref='y9',
#            text="Lockdown to peak: <br> 21 days",font=dict( color="black",size=14))
fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-02-23',date_max]), row=5, col=1,showgrid=True)
yr=6000
fig.update_yaxes(title_text='Case',range=[0,yr], row=5, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=5, col=1, secondary_y=True,color='blue')



fig.update_layout(height=2500, width=800, showlegend=True, title_text="Daily New Adds",
                 font=dict(
        family="Arial",
        size=14,
        color="black"
    )
                
               )

fig.show()

#fig.write_image("output/daily_newadd_death_"+ dfa.Date_updated.max().strftime('_%m_%d_%Y')+".jpeg", scale=3)
In [16]:
dcan=df[df.Country=='Canada'].copy()

dcan.loc[:,'StartDate']=-999
for j in dcan.Region.unique():
    sdate=dcan.loc[(dcan.Region==j) & (dcan.Confirmed > 100),'Date_updated'].min()
    ##print(f"Country: {j} case number: {dfa[(dfa.Country==j) & (dfa.Confirmed > 100)]['Confirmed'].min()} start date {sdate}")
    tmp=dcan.loc[dcan.Region==j,'Date_updated']-sdate
    dcan.loc[dcan.Region==j,'StartDate']=tmp.dt.days + 1
    

COVID-19 cases by province

In [17]:
#date_max='2020-07-10'

fig = make_subplots(
    rows=4, cols=1, 
    subplot_titles=("Ontario", "Quebec","Alberta","British Columbia"),
    vertical_spacing=0.08,
    specs=[[{"secondary_y": True}],
           [{"secondary_y": True}],
           [{"secondary_y": True}],
           [{"secondary_y": True}]])

bw=1000*3600*24

oneday=86400000.0



##Ontario
ixd=(df.Country=='Canada') & (df.Region =='Ontario')
fig.add_trace(go.Bar(x=df.loc[ixd ,'Date_updated'],
                y=df.loc[ixd,'Confirmed_added'] ,
                name='Case',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5,showlegend=False                     
                ),
             row=1, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=df.loc[ixd,'Date_updated'],
                y=df.loc[ixd,'Death_added'] ,
                name='Death',
                marker_color='blue',
                width=bw*0.5,showlegend=False     
                ),
             row=1, col=1, secondary_y=True)

fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-03-10',date_max]), row=1, col=1,showgrid=True)
yr=800
fig.update_yaxes(title_text='Case',range=[0,yr], row=1, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=1, col=1, secondary_y=True,color='blue')


#Quebec
ixd=(df.Country=='Canada') & (df.Region =='Quebec')
fig.add_trace(go.Bar(x=df.loc[ixd ,'Date_updated'],
                y=df.loc[ixd,'Confirmed_added'] ,
                name='Case',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5  ,showlegend=False                   
                ),
             row=2, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=df.loc[ixd,'Date_updated'],
                y=df.loc[ixd,'Death_added'] ,
                name='Death',
                marker_color='blue',
                width=bw*0.5   ,showlegend=False  
                ),
             row=2, col=1, secondary_y=True)

fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-03-10',date_max]), row=2, col=1,showgrid=True)
yr=2250
fig.update_yaxes(title_text='Case',range=[0,yr], row=2, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/2.], row=2, col=1, secondary_y=True,color='blue')

#Alberta
ixd=(df.Country=='Canada') & (df.Region =='Alberta')
fig.add_trace(go.Bar(x=df.loc[ixd ,'Date_updated'],
                y=df.loc[ixd,'Confirmed_added'] ,
                name='Case',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5  ,showlegend=False                   
                ),
             row=3, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=df.loc[ixd,'Date_updated'],
                y=df.loc[ixd,'Death_added'] ,
                name='Death',
                marker_color='blue',
                width=bw*0.5   ,showlegend=False  
                ),
             row=3, col=1, secondary_y=True)

fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-03-10',date_max]), row=3, col=1,showgrid=True)
yr=400
fig.update_yaxes(title_text='Case',range=[0,yr], row=3, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=3, col=1, secondary_y=True,color='blue')

#British Columbia
ixd=(df.Country=='Canada') & (df.Region =='British Columbia')
fig.add_trace(go.Bar(x=df.loc[ixd ,'Date_updated'],
                y=df.loc[ixd,'Confirmed_added'] ,
                name='Case',
                marker_color='rgb(55, 83, 109)',
                width=bw*0.5  ,showlegend=False                   
                ),
             row=4, col=1, secondary_y=False)
fig.add_trace(go.Bar(x=df.loc[ixd,'Date_updated'],
                y=df.loc[ixd,'Death_added'] ,
                name='Death',
                marker_color='blue',
                width=bw*0.5   ,showlegend=False  
                ),
             row=4, col=1, secondary_y=True)

fig.update_xaxes(title_text="Date", showline=True,linewidth=1,showticklabels=True,
                 linecolor='black',ticklen=10,tickwidth=2,tickcolor='black',dtick=7*oneday,tickformat= "%d-%b",
                 tickangle=30,
                 ticks='outside',range=pd.to_datetime(['2020-03-10',date_max]), row=4, col=1,showgrid=True)
yr=300
fig.update_yaxes(title_text='Case',range=[0,yr], row=4, col=1, secondary_y=False)
fig.update_yaxes(title_text='Death',range=[0,yr/5.], row=4, col=1, secondary_y=True,color='blue')




fig.update_layout(height=2000, width=800, showlegend=True, title_text="Daily New Adds",
                 font=dict(
        family="Arial",
        size=14,
        color="black"
    )
                
               )




fig.show()

#fig.write_image("output/daily_newadd_death_Canada_"+ dfa.Date_updated.max().strftime('_%m_%d_%Y')+".jpeg", scale=3)

Long Term Outlook

The exponential projection model

In [18]:
xlong=list(range(0,1000))
xdate_long=[pd.to_datetime('2020-01-22')+np.timedelta64(x,'D') for x in xlong]
sce=pd.DataFrame({'x': xlong, 'date': xdate_long, \
                  'case': float('NaN'), 'case_city': float('NaN'),\
                 'case_sm': float('NaN'), 'case_city_sm': float('NaN')}).set_index('date')
dft_dl=pd.read_excel("https://docs.google.com/uc?export=download&id=1euhrML0rkV_hHF1thiA0G5vSSeZCqxHY",\
                     sheet_name="Cases by Reported Date").set_index('Reported Date')
sce.loc[dft_dl.index,'case_city']=dft_dl['Case Count']
sce.loc[(sce.index <= dft_dl.index.max()) & (sce.case_city.isna()),'case_city']=0

sce.loc[dfa.Date_updated[dfa.Country=='Canada'],'case']=dfa.loc[dfa.Country=='Canada','Confirmed_added'].values
sce.loc[(sce.index < dfa.Date_updated[dfa.Country=='Canada'].max()) & (sce.case.isna()),'case']=0

sce['case_city_sm']=sce['case_city'].rolling(5,min_periods=1).mean()
sce['case_sm']=sce['case'].rolling(5,min_periods=1).mean()

sce.loc[sce['case'].isna(),'case_sm']=float("NaN")
sce.loc[sce['case_city'].isna(),'case_city_sm']=float("NaN")
In [19]:
#two gaussian fit for Canada
popt=[87.60058132,  22.35279574, 1639.36962114,  187.78573285, 53.77234086,  415.11806907]
sce['Canada_fit']=gauss_two(sce.x,*popt)

tsep=150
#Peak and Valley

sc=sce['Canada_fit'] 
for i in range(1,6):
    p=[popt[0]+ tsep*i+35, popt[1], popt[2]*0.95**i, popt[3] + tsep*i, popt[4], popt[5]*0.95**i]
    sc=sc+gauss_two(sce.x,*p)
sce['sc1 Canada']=sc

sc=sce['Canada_fit']
sc=sc+gauss_one(sce.x, popt[0]+183, popt[1], popt[2]*2.) \
    + gauss_one(sce.x, popt[3]+183, popt[4], popt[5]*0.95)
for i in range(2,5):
    p=[popt[0]+ tsep*i, popt[1], popt[2]*0.9**i, popt[3] + tsep*i, popt[4], popt[5]*0.95**i]
    sc=sc+gauss_two(sce.x,*p)
sce['sc2 Canada']=sc

sc=sce['Canada_fit'] 
for i in range(1,10):
    p=[popt[3]  + tsep/2*i , popt[4]*0.5, popt[5]*0.98**i]
    sc=sc+gauss_one(sce.x,*p)
sce['sc3 Canada']=sc

sce.reset_index(inplace=True)
In [20]:
fig = make_subplots(rows=1, cols=1, specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(x=sce.date,y=sce['sc1 Canada'], fill='tozeroy'
               ,mode="none",fillcolor='rgba(173,216,230,0.7)', name='Scenario'))

fig.add_trace(go.Scatter(x=sce.date,y=sce.case_sm,mode='lines', name='Case - Canada',
                         line_color='rgb(128, 128, 128)'))
fig.add_trace(go.Scatter(x=sce.date,y=sce.case_city_sm,mode='lines', name='Case - Toronto' ,
                         line_color='rgb(0,128,0)'), secondary_y=True)

fig.update_yaxes(tickvals=[1000,2000,3000,4000], title_text='Canada Daily Adds',range=[0,4000]) 
fig.update_yaxes(tickvals=[125,250,375,500], title_text='Toronto Daily Adds',range=[0,4000/8.],
                 secondary_y=True,color='rgb(0,128,0)')
fig.update_xaxes(title_text='Date',range=['2020-02-01','2022-01-30'])
fig.update_layout(height=500, width=800, 
                  title_text="Scenario 1: Peaks and Valleys",
                  title_x=0.85,
                  title_y=0.9,
                 font=dict(family="Arial, sans-serif",size=16,color="black"),
                 margin=dict(l=20, r=20, t=20, b=20),
                 legend=dict(bgcolor='rgba(0,0,0,0)',
    yanchor="top",
    y=0.8,
    xanchor="left",
    x=0.7))

fig.show()
In [21]:
fig = make_subplots(rows=1, cols=1, specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(x=sce.date,y=sce['sc2 Canada'], fill='tozeroy'
               ,mode="none",fillcolor='rgba(173,216,230,0.7)', name='Scenario'))

fig.add_trace(go.Scatter(x=sce.date,y=sce.case_sm,mode='lines', name='Case - Canada',
                         line_color='rgb(128, 128, 128)'))
fig.add_trace(go.Scatter(x=sce.date,y=sce.case_city_sm,mode='lines', name='Case - Toronto' ,
                         line_color='rgb(0,128,0)'), secondary_y=True)

fig.update_yaxes(tickvals=[1000,2000,3000,4000], title_text='Canada Daily Adds',range=[0,4000]) 
fig.update_yaxes(tickvals=[125,250,375,500], title_text='Toronto Daily Adds',range=[0,4000/8.],
                 secondary_y=True,color='rgb(0,128,0)')
fig.update_xaxes(title_text='Date',range=['2020-02-01','2022-01-30'])
fig.update_layout(height=500, width=800, 
                  title_text="Scenario 2: Fall Peak",
                  title_x=0.85,
                  title_y=0.9,
                 font=dict(family="Arial, sans-serif",size=16,color="black"),
                 margin=dict(l=20, r=20, t=20, b=20),
                 legend=dict(bgcolor='rgba(0,0,0,0)',
    yanchor="top",
    y=0.8,
    xanchor="left",
    x=0.7))

fig.show()
In [22]:
fig = make_subplots(rows=1, cols=1, specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(x=sce.date,y=sce['sc3 Canada'], fill='tozeroy'
               ,mode="none",fillcolor='rgba(173,216,230,0.7)', name='Scenario'))

fig.add_trace(go.Scatter(x=sce.date,y=sce.case_sm,mode='lines', name='Case - Canada',
                         line_color='rgb(128, 128, 128)'))
fig.add_trace(go.Scatter(x=sce.date,y=sce.case_city_sm,mode='lines', name='Case - Toronto' ,
                         line_color='rgb(0,128,0)'), secondary_y=True)

fig.update_yaxes(tickvals=[1000,2000,3000,4000], title_text='Canada Daily Adds',range=[0,4000]) 
fig.update_yaxes(tickvals=[125,250,375,500], title_text='Toronto Daily Adds',range=[0,4000/8.],
                 secondary_y=True,color='rgb(0,128,0)')
fig.update_xaxes(title_text='Date',range=['2020-02-01','2022-01-30'])
fig.update_layout(height=500, width=800, 
                  title_text="Scenario 3: Slow Burn",
                  title_x=0.85,
                  title_y=0.9,
                 font=dict(family="Arial, sans-serif",size=16,color="black"),
                 margin=dict(l=20, r=20, t=20, b=20),
                 legend=dict(bgcolor='rgba(0,0,0,0)',
    yanchor="top",
    y=0.8,
    xanchor="left",
    x=0.7))